/*
 * This file is part of the Xilinx DMA IP Core driver for Linux
 *
 * Copyright (c) 2016-present,  Xilinx, Inc.
 * All rights reserved.
 *
 * This source code is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * The full GNU General Public License is included in this distribution in
 * the file called "COPYING".
 */

#define pr_fmt(fmt)     KBUILD_MODNAME ":%s: " fmt, __func__

#include <linux/ioctl.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/aer.h>
/* include early, to verify it depends only on the headers above */
#include "libxdma_api.h"
#include "libxdma.h"
#include "xdma_mod.h"
#include "xdma_cdev.h"
#include "version.h"

#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h> 
#include <linux/uaccess.h>  

#define DRV_MODULE_NAME		"xdma"
#define DRV_MODULE_DESC		"Xilinx XDMA Reference Driver"

static char version[] =
	DRV_MODULE_DESC " " DRV_MODULE_NAME " v" DRV_MODULE_VERSION "\n";

MODULE_AUTHOR("Xilinx, Inc.");
MODULE_DESCRIPTION(DRV_MODULE_DESC);
MODULE_VERSION(DRV_MODULE_VERSION);
MODULE_LICENSE("Dual BSD/GPL");

/* SECTION: Module global variables */
static int xpdev_cnt;

#define  ALL_PCI_IDS_NUM		500

static struct pci_device_id pci_ids[ALL_PCI_IDS_NUM] = {
	{ PCI_DEVICE(0x10ee, 0x9048), },
	{ PCI_DEVICE(0x10ee, 0x9044), },
	{ PCI_DEVICE(0x10ee, 0x9042), },
	{ PCI_DEVICE(0x10ee, 0x9041), },
	{ PCI_DEVICE(0x10ee, 0x903f), },
	{ PCI_DEVICE(0x10ee, 0x9038), },
	{ PCI_DEVICE(0x10ee, 0x9028), },
	{ PCI_DEVICE(0x10ee, 0x9018), },
	{ PCI_DEVICE(0x10ee, 0x9034), },
	{ PCI_DEVICE(0x10ee, 0x9024), },
	{ PCI_DEVICE(0x10ee, 0x9014), },
	{ PCI_DEVICE(0x10ee, 0x9032), },
	{ PCI_DEVICE(0x10ee, 0x9022), },
	{ PCI_DEVICE(0x10ee, 0x9012), },
	{ PCI_DEVICE(0x10ee, 0x9031), },
	{ PCI_DEVICE(0x10ee, 0x9021), },
	{ PCI_DEVICE(0x10ee, 0x9011), },

	{ PCI_DEVICE(0x10ee, 0x8011), },
	{ PCI_DEVICE(0x10ee, 0x8012), },
	{ PCI_DEVICE(0x10ee, 0x8014), },
	{ PCI_DEVICE(0x10ee, 0x8018), },
	{ PCI_DEVICE(0x10ee, 0x8021), },
	{ PCI_DEVICE(0x10ee, 0x8022), },
	{ PCI_DEVICE(0x10ee, 0x8024), },
	{ PCI_DEVICE(0x10ee, 0x8028), },
	{ PCI_DEVICE(0x10ee, 0x8031), },
	{ PCI_DEVICE(0x10ee, 0x8032), },
	{ PCI_DEVICE(0x10ee, 0x8034), },
	{ PCI_DEVICE(0x10ee, 0x8038), },

	{ PCI_DEVICE(0x10ee, 0x7011), },
	{ PCI_DEVICE(0x10ee, 0x7012), },
	{ PCI_DEVICE(0x10ee, 0x7014), },
	{ PCI_DEVICE(0x10ee, 0x7018), },
	{ PCI_DEVICE(0x10ee, 0x7021), },
	{ PCI_DEVICE(0x10ee, 0x7022), },
	{ PCI_DEVICE(0x10ee, 0x7024), },
	{ PCI_DEVICE(0x10ee, 0x7028), },
	{ PCI_DEVICE(0x10ee, 0x7031), },
	{ PCI_DEVICE(0x10ee, 0x7032), },
	{ PCI_DEVICE(0x10ee, 0x7034), },
	{ PCI_DEVICE(0x10ee, 0x7038), },

	{ PCI_DEVICE(0x10ee, 0x6828), },
	{ PCI_DEVICE(0x10ee, 0x6830), },
	{ PCI_DEVICE(0x10ee, 0x6928), },
	{ PCI_DEVICE(0x10ee, 0x6930), },
	{ PCI_DEVICE(0x10ee, 0x6A28), },
	{ PCI_DEVICE(0x10ee, 0x6A30), },
	{ PCI_DEVICE(0x10ee, 0x6D30), },

	{ PCI_DEVICE(0x10ee, 0x4808), },
	{ PCI_DEVICE(0x10ee, 0x4828), },
	{ PCI_DEVICE(0x10ee, 0x4908), },
	{ PCI_DEVICE(0x10ee, 0x4A28), },
	{ PCI_DEVICE(0x10ee, 0x4B28), },

	{ PCI_DEVICE(0x10ee, 0x2808), },

#ifdef INTERNAL_TESTING
	{ PCI_DEVICE(0x1d0f, 0x1042), 0},
#endif
	/* aws */
	{ PCI_DEVICE(0x1d0f, 0xf000), },
	{ PCI_DEVICE(0x1d0f, 0xf001), },

	{ PCI_DEVICE(0x10ee, 0x5011), },
	{ PCI_DEVICE(0x10ee, 0x5012), },
	{ PCI_DEVICE(0x10ee, 0x5014), },
	{ PCI_DEVICE(0x10ee, 0x5018), },
	{ PCI_DEVICE(0x10ee, 0x5021), },
	{ PCI_DEVICE(0x10ee, 0x5022), },
	{ PCI_DEVICE(0x10ee, 0x5024), },
	{ PCI_DEVICE(0x10ee, 0x5028), },
	{ PCI_DEVICE(0x10ee, 0x5031), },
	{ PCI_DEVICE(0x10ee, 0x5032), },
	{ PCI_DEVICE(0x10ee, 0x5034), },
	{ PCI_DEVICE(0x10ee, 0x5038), },

	{0,}
};
MODULE_DEVICE_TABLE(pci, pci_ids);

static void xpdev_free(struct xdma_pci_dev *xpdev)
{
	struct xdma_dev *xdev = xpdev->xdev;

	pr_info("xpdev 0x%p, destroy_interfaces, xdev 0x%p.\n", xpdev, xdev);
	xpdev_destroy_interfaces(xpdev);
	xpdev->xdev = NULL;
	pr_info("xpdev 0x%p, xdev 0x%p xdma_device_close.\n", xpdev, xdev);
	xdma_device_close(xpdev->pdev, xdev);
	xpdev_cnt--;

	kfree(xpdev);
}

static struct xdma_pci_dev *xpdev_alloc(struct pci_dev *pdev)
{
	struct xdma_pci_dev *xpdev = kmalloc(sizeof(*xpdev), GFP_KERNEL);

	if (!xpdev)
		return NULL;
	memset(xpdev, 0, sizeof(*xpdev));

	xpdev->magic = MAGIC_DEVICE;
	xpdev->pdev = pdev;
	xpdev->user_max = MAX_USER_IRQ;
	xpdev->h2c_channel_max = XDMA_CHANNEL_NUM_MAX;
	xpdev->c2h_channel_max = XDMA_CHANNEL_NUM_MAX;

	xpdev_cnt++;
	return xpdev;
}

static int probe_one(struct pci_dev *pdev, const struct pci_device_id *id)
{
	int rv = 0;
	struct xdma_pci_dev *xpdev = NULL;
	struct xdma_dev *xdev;
	void *hndl;

	unsigned short pci_vendor_id, pci_device_id, pci_slot_id, pci_bus_id;
	int dev_num_num;

	char bus_id[3];  
    char slot_id[3]; 

	// 从字符串中提取槽位号和总线号并存储到slot_id变量中  
    strncpy(slot_id, strchr(dev_name(&pdev->dev), ':') + 1, 2);  
    slot_id[2] = '\0'; // 添加字符串结束符  
    strncpy(bus_id, strchr(dev_name(&pdev->dev), ':') + 4, 2);  
    bus_id[2] = '\0'; // 添加字符串结束符  

	// 将提取的值存储到pci_slot_id和pci_bus_id变量中  
	pci_slot_id = (unsigned short)simple_strtol(slot_id, NULL, 16);
	pci_bus_id = (unsigned short)simple_strtol(bus_id, NULL, 16);

	// 获取pci设备的厂商id，设备id，pci槽位信息
    pci_vendor_id = pdev->vendor;
    pci_device_id = pdev->device;

	printk("pci probe vendor: %x, device: %x, slot: %x, bus: %x\n", \
		pci_vendor_id, \
		pci_device_id, \
		pci_slot_id, \
		pci_bus_id);

	// 修改文件名，用于添加槽位号信息
	for(dev_num_num = 0; dev_num_num < 9; dev_num_num ++){

		// 修改文件名字
		devnode_names[dev_num_num][4] = slot_id[0];
		devnode_names[dev_num_num][5] = slot_id[1];

		// 修改文件名字
		devnode_names[dev_num_num][7] = bus_id[0];
		devnode_names[dev_num_num][8] = bus_id[1];

		printk("devnode_names[dev_num_num]: %s\n", devnode_names[dev_num_num]);
	}

	xpdev = xpdev_alloc(pdev);
	if (!xpdev)
		return -ENOMEM;

	hndl = xdma_device_open(DRV_MODULE_NAME, pdev, &xpdev->user_max,
			&xpdev->h2c_channel_max, &xpdev->c2h_channel_max);
	if (!hndl) {
		rv = -EINVAL;
		goto err_out;
	}

	if (xpdev->user_max > MAX_USER_IRQ) {
		pr_err("Maximum users limit reached\n");
		rv = -EINVAL;
		goto err_out;
	}

	if (xpdev->h2c_channel_max > XDMA_CHANNEL_NUM_MAX) {
		pr_err("Maximun H2C channel limit reached\n");
		rv = -EINVAL;
		goto err_out;
	}

	if (xpdev->c2h_channel_max > XDMA_CHANNEL_NUM_MAX) {
		pr_err("Maximun C2H channel limit reached\n");
		rv = -EINVAL;
		goto err_out;
	}

	if (!xpdev->h2c_channel_max && !xpdev->c2h_channel_max)
		pr_warn("NO engine found!\n");

	if (xpdev->user_max) {
		u32 mask = (1 << (xpdev->user_max + 1)) - 1;

		rv = xdma_user_isr_enable(hndl, mask);
		if (rv)
			goto err_out;
	}

	/* make sure no duplicate */
	xdev = xdev_find_by_pdev(pdev);
	if (!xdev) {
		pr_warn("NO xdev found!\n");
		rv =  -EINVAL;
		goto err_out;
	}

	if (hndl != xdev) {
		pr_err("xdev handle mismatch\n");
		rv =  -EINVAL;
		goto err_out;
	}

	pr_info("%s xdma%d, pdev 0x%p, xdev 0x%p, 0x%p, usr %d, ch %d,%d.\n",
		dev_name(&pdev->dev), xdev->idx, pdev, xpdev, xdev,
		xpdev->user_max, xpdev->h2c_channel_max,
		xpdev->c2h_channel_max);

	xpdev->xdev = hndl;

	rv = xpdev_create_interfaces(xpdev);
	if (rv)
		goto err_out;

	dev_set_drvdata(&pdev->dev, xpdev);

	return 0;

err_out:
	pr_err("pdev 0x%p, err %d.\n", pdev, rv);
	xpdev_free(xpdev);
	return rv;
}

static void remove_one(struct pci_dev *pdev)
{
	struct xdma_pci_dev *xpdev;

	if (!pdev)
		return;

	xpdev = dev_get_drvdata(&pdev->dev);
	if (!xpdev)
		return;

	pr_info("pdev 0x%p, xdev 0x%p, 0x%p.\n",
		pdev, xpdev, xpdev->xdev);
	xpdev_free(xpdev);

	dev_set_drvdata(&pdev->dev, NULL);
}

static pci_ers_result_t xdma_error_detected(struct pci_dev *pdev,
					pci_channel_state_t state)
{
	struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);

	switch (state) {
	case pci_channel_io_normal:
		return PCI_ERS_RESULT_CAN_RECOVER;
	case pci_channel_io_frozen:
		pr_warn("dev 0x%p,0x%p, frozen state error, reset controller\n",
			pdev, xpdev);
		xdma_device_offline(pdev, xpdev->xdev);
		pci_disable_device(pdev);
		return PCI_ERS_RESULT_NEED_RESET;
	case pci_channel_io_perm_failure:
		pr_warn("dev 0x%p,0x%p, failure state error, req. disconnect\n",
			pdev, xpdev);
		return PCI_ERS_RESULT_DISCONNECT;
	}
	return PCI_ERS_RESULT_NEED_RESET;
}

static pci_ers_result_t xdma_slot_reset(struct pci_dev *pdev)
{
	struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);

	pr_info("0x%p restart after slot reset\n", xpdev);
	if (pci_enable_device_mem(pdev)) {
		pr_info("0x%p failed to renable after slot reset\n", xpdev);
		return PCI_ERS_RESULT_DISCONNECT;
	}

	pci_set_master(pdev);
	pci_restore_state(pdev);
	pci_save_state(pdev);
	xdma_device_online(pdev, xpdev->xdev);

	return PCI_ERS_RESULT_RECOVERED;
}

static void xdma_error_resume(struct pci_dev *pdev)
{
	struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);

	pr_info("dev 0x%p,0x%p.\n", pdev, xpdev);
#if KERNEL_VERSION(5, 7, 0) <= LINUX_VERSION_CODE
	pci_aer_clear_nonfatal_status(pdev);
#else
	pci_cleanup_aer_uncorrect_error_status(pdev);
#endif
}

#if KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE
static void xdma_reset_prepare(struct pci_dev *pdev)
{
	struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);

	pr_info("dev 0x%p,0x%p.\n", pdev, xpdev);
	xdma_device_offline(pdev, xpdev->xdev);
}

static void xdma_reset_done(struct pci_dev *pdev)
{
	struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);

	pr_info("dev 0x%p,0x%p.\n", pdev, xpdev);
	xdma_device_online(pdev, xpdev->xdev);
}

#elif KERNEL_VERSION(3, 16, 0) <= LINUX_VERSION_CODE
static void xdma_reset_notify(struct pci_dev *pdev, bool prepare)
{
	struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);

	pr_info("dev 0x%p,0x%p, prepare %d.\n", pdev, xpdev, prepare);

	if (prepare)
		xdma_device_offline(pdev, xpdev->xdev);
	else
		xdma_device_online(pdev, xpdev->xdev);
}
#endif

static const struct pci_error_handlers xdma_err_handler = {
	.error_detected	= xdma_error_detected,
	.slot_reset	= xdma_slot_reset,
	.resume		= xdma_error_resume,
#if KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE
	//.reset_prepare	= xdma_reset_prepare,
	//.reset_done	= xdma_reset_done,
#elif KERNEL_VERSION(3, 16, 0) <= LINUX_VERSION_CODE
	//.reset_notify	= xdma_reset_notify,
#endif
};

static struct pci_driver pci_driver = {
	.name = DRV_MODULE_NAME,
	.id_table = pci_ids,
	.probe = probe_one,
	.remove = remove_one,
	.err_handler = &xdma_err_handler,
};

#define MAX_IDS 1000  
unsigned int ids_data[MAX_IDS];  
  
static int my_read_driver(char *file_path, unsigned int *ids_data) {  
    struct file *fp = NULL;  
    long int file_size;  
    char *buf;  
    int ret = 0;  
	mm_segment_t fs;

	long int buf_size = 0;
	long int temp_i = 0, temp_j = 0;
	unsigned int opt_data = 0;
	unsigned int ids_vendor_id = 0, ids_device_id = 0;

	// 打开根文件系统中的文件
    fp = filp_open(file_path, O_RDWR, 0666);  
    if (IS_ERR(fp)) {  
        printk(KERN_ERR "Failed to open file %s\n", file_path);  
        return PTR_ERR(fp);  
    }  
	printk("pxie driver use ids file name: %s\n", file_path);

	// 操作根文件系统需要
	fs = get_fs();
    set_fs(KERNEL_DS); 
	
	// 获取文件的大小
    file_size = fp->f_path.dentry->d_inode->i_size;  
	printk("pxie ids file size: %ld\n", file_size);
    buf = kzalloc(file_size + 64, GFP_KERNEL);  
    if (buf == NULL) {  
        printk(KERN_ERR "Failed to allocate memory\n");  
        ret = -ENOMEM;  
        goto out;  
    }  

	// 每次从头开始读
	fp->f_pos = 0;
	printk("open file pos: %lld\n", fp->f_pos);

	// 实际读取的长度
	file_size = vfs_read(fp, buf, file_size, &fp->f_pos) ;
	printk("real read size: %ld\n", file_size);

	printk("pxie ids: \n%s\n", buf);

	set_fs(fs);

	// 一共多少条板卡号
	buf_size = file_size / 10;
	printk("ids list num: %ld\n", buf_size);

	// 计算厂商ID和设备ID
	for(temp_i = 0; temp_i < buf_size; temp_i ++){
		// 获取vendor id
		ids_vendor_id = 0;
		for(temp_j = 0; temp_j < 4; temp_j ++){
			ids_vendor_id <<= 4;

			opt_data = 0;
			opt_data = buf[temp_i * 10 + temp_j];

			if((opt_data >= 0x61) && (opt_data <= 0x66)){
				opt_data = opt_data - 0x61 + 0xa;
				ids_vendor_id |= (opt_data & 0xf);
			} else if((opt_data >= 0x30) && (opt_data <= 0x39)){
				opt_data = opt_data - 0x30;
				ids_vendor_id |= (opt_data & 0xf);
			} else {
				ids_vendor_id = 0;
			}
		}
		
		// 获取device id
		ids_device_id = 0;
		for(temp_j = 0; temp_j < 4; temp_j ++){
			ids_device_id <<= 4;

			opt_data = 0;
			opt_data = buf[temp_i * 10 + temp_j + 5];

			if((opt_data >= 0x61) && (opt_data <= 0x66)){
				opt_data = opt_data - 0x61 + 0xa;
				ids_device_id |= (opt_data & 0xf);
			} else if((opt_data >= 0x30) && (opt_data <= 0x39)){
				opt_data = opt_data - 0x30;
				ids_device_id |= (opt_data & 0xf);
			} else {
				ids_device_id = 0;
			}
		}

		// 更新注册的vendor id和device id
		pci_ids[temp_i].vendor = ids_vendor_id;
		pci_ids[temp_i].device = ids_device_id;
	}

	// 给出提示信息
	for(temp_i = 0; temp_i < buf_size; temp_i ++){
		printk("pci_ids[%ld] msg: %x, %x, %x, %x\n", temp_i, pci_ids[temp_i].vendor, pci_ids[temp_i].device, pci_ids[temp_i].subvendor, pci_ids[temp_i].subdevice);   
	}

    kfree(buf);  
out:  
    filp_close(fp, NULL);  
    return ret;  
}  

static int xdma_mod_init(void)
{
	int rv;
	unsigned int temp_i;

	pr_info("%s", version);

	if (desc_blen_max > XDMA_DESC_BLEN_MAX)
		desc_blen_max = XDMA_DESC_BLEN_MAX;
	pr_info("desc_blen_max: 0x%x/%u, timeout: h2c %u c2h %u sec.\n",
		desc_blen_max, desc_blen_max, h2c_timeout, c2h_timeout);

	// 清空现有的PCI表格
	for(temp_i = 0; temp_i < ALL_PCI_IDS_NUM; temp_i ++){
		pci_ids[0].vendor = 0;
		pci_ids[0].device = 0;
		pci_ids[0].subvendor = 0xffffffff;
		pci_ids[0].subdevice = 0xffffffff;
	}

	// 读取设备编号的ids文件
	my_read_driver("/root/hitmc_app/pxie_ids_file", ids_data);  

	rv = xdma_cdev_init();
	if (rv < 0)
		return rv;

	return pci_register_driver(&pci_driver);
}

static void xdma_mod_exit(void)
{
	/* unregister this driver from the PCI bus driver */
	dbg_init("pci_unregister_driver.\n");
	pci_unregister_driver(&pci_driver);
	xdma_cdev_cleanup();
}

module_init(xdma_mod_init);
module_exit(xdma_mod_exit);
